class Console
  
  @@index = "";
  @@before_filters = [];
  @@after_filters = [];
  
  OptionMethod = "option_%s";
  
  class NoCommandError < StandardError; end
  class NoFilterError < StandardError; end
  
  class << self
    
    alias_method :start, :new;
    
    protected
    def page_separator(separator)
      @@page_separator = separator;
    end
    
    def index(*args)
      @@index = args.join "\n";
    end
    
    def filter(order, methods, opts={})
      [:except, :only].each{|opt| opts[opt] = [opts[opt]].flatten if opts[opt];};
      methods = methods.map{|method| opts.merge(:filter => method);};
      class_variable_get("@@#{order}_filters").concat(methods);
    end
    
    def before_filter(*methods)
      opts = (methods.last.kind_of?(Hash)) ? methods.pop : {};
      filter(:before, methods.flatten, opts);
    end
    
    def after_filter(*methods)
      opts = (methods.last.kind_of?(Hash)) ? methods.pop : {};
      filter(:after, methods.flatten, opts);
    end
    
    def option(number, &block)
      define_method(OptionMethod % number, &block);
    end
    
  end
  
  def initialize
    @__run__ = true;
    run;
  end
  
  def index
    print @@index;
  end
  
  def exit
    @__run__ = false;
  end
  
  def method_missing(command, *args)
    raise NoCommandError, "No command responds to #{command}";
  end
  
  protected
  def ask_params(params)
    params = params.map do |param, label|
      $stdout.print "#{label} ";
      [param, $stdin.gets.chomp];      
    end
    Hash[*params.flatten];
  end
  
  def run
    $stdout.sync = true;
    while @__run__
      index;
      @command = $stdin.gets.chomp.to_sym;
      run_filters(@@before_filters, @command);
      run_command(@command);
      run_filters(@@after_filters, @command);
    end
  end
  
  def run_command(command)
    send(method_name(command));
  end
  
  def run_filters(filters, command)
    command = method_name(command);
    filters.each do |filter|
      exclusions = (filter[:except]) ? filter[:except].map{|cmd| method_name(cmd);} : nil;
      inclusions = (filter[:only]) ? filter[:only].map{|cmd| method_name(cmd);} : nil; 
      unless command == :exit
        method = filter[:filter].to_sym;
        if (inclusions and inclusions.include?(command)) or 
          (exclusions and not exclusions.include?(command)) or 
          (not exclusions and not inclusions)
          if (public_methods + protected_methods + private_methods).include?(method.to_s)
            send(method);
          else
            raise NoFilterError, "There is no filter called #{filter[:filter]}";
          end
        end #if
      end #unless
    end #each
  end
  
  def method_name(command)
    method = "#{command}";
    method = OptionMethod % method if method =~ /^\d+$/;
    method.to_sym;
  end
  
end